/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package crawler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Анализатор забирает страницы из очереди страниц загруженных downloader'ом
 * и обрабатывает их. Созданные в процессе анализа объекты Page будут
 * отправляться главному модулю на хранение.
 * @author Павельев Антон
 */

public class Analyzer implements Runnable{
    
    private Thread thread;
    /**флаг остановки потока**/
    public static boolean stopped=false;
    private static boolean analyzerRunned=false;
    /**Массив внешних анкоров**/
    public static ArrayList<Anchor> externalAnchors = new ArrayList<Anchor>();
    /**Отвечает за правила robots.txt**/
    RobotsTxt robots;

    public class RobotsTxt{
        ArrayList<String> file;
        ArrayList<String> rules;
        HashMap<String, Boolean> rulesSet;

        public RobotsTxt() {
            file = Downloader.getRobotsTxt(Downloader.baseURL);
            rules = new ArrayList<String>();
            rulesSet = new HashMap<String, Boolean>();

            boolean bot = false;
            String botStr = "*";
            if(Options.isRobotsGoogle())
                botStr = "Google";
            if(Options.isRobotsYandex())
                botStr = "Yandex";

            for (String str : file) {
                if(str.contains("User-agent") && (str.contains(botStr) || str.contains("*")))
                    bot = true;
                if(bot)
                {
                    if(str.contains("Disallow: ")){
                        rules.add(str.substring(10));
                        rulesSet.put(str.substring(10), false);
                    }
                    if(str.contains("Allow: ")){
                        rules.add(str.substring(7));
                        rulesSet.put(str.substring(7), true);
                    }
                }
                if(str.equals("\n"))
                    bot = false;
            }
            for(int i=0;i<rules.size()-1;i++)
                for(int j=i;j<rules.size()-i-1;j++)
                    if(rules.get(j).length()>rules.get(j+1).length())
                    {
                        String buf = rules.get(j);
                        rules.set(j, rules.get(j+1));
                        rules.set(j+1, buf);
                    }
        }

        public boolean isAllow(String url){
            boolean res = false;
            for (String str : rules) {
                if(url.contains(str))
                    res = rulesSet.get(str);
            }
            return res;
        }


    }

    /**
     * Класс для анкора
     */
    public class Anchor{

        public Anchor(String fromURL, String toURL, String text) {
            this.fromURL = fromURL;
            this.toURL = toURL;
            this.text = text;
        }
        /**С какого адреса ссылка**/
        public String fromURL;
        /**Куда ведёт ссылка**/
        public String toURL;
        /**Сам анкор**/
        public String text;
    }

    public Analyzer(){
        if (!analyzerRunned) {
            analyzerRunned = true;
            thread = new Thread(this);
            thread.setName("Analyzer-thr");
            stopped = false;
        } else {
            System.err.println("Невозможно создать более одного анализатора одновременно");
        }
    }
    
    @Override
    public void run(){
        System.out.println("Analyzer.run работает в потоке: "+Thread.currentThread().getName());
        if(Options.isUsingRobotsTxt())
            robots = new RobotsTxt();
        while (!stopped) {
            Downloader.UnparsedPage page = Downloader.getDownloaded();
            //Если очередь пуста, то отправляем поток в ожидание
            if (page==null) {
                try {
                    if(Downloader.MEGAFLAG==false&&Downloader.getQueueSize()==0)
                        break;
                    System.out.println("Очередь на обработку пуста. Поток анализатора ожидает: вызова notify/ попробует опросить очередь через две секунды");
                    synchronized(Downloader.downloaded){
                        Downloader.downloaded.wait(2000);
                    }
                } catch (InterruptedException ex) {
                    System.err.println("Не удалось приостановить поток анализатора");
                }
            //иначе парсим страницу    
            } else {
                analyze(page);
            }
        }
        Downloader.stopped=true;
        System.out.println("Поток Analyzer ("+Thread.currentThread().getName()+") завершился.");
    }

     /**
     * 
     * @param up объект описывающий нераспарсенную страницу
     */
    public void analyze(Downloader.UnparsedPage up){
        // если robots.txt запрещает ходить по данному url уходим со страницы
        if(robots!=null)
            if(!robots.isAllow(up.url))
                return;

        Page page = new Page();
        String s = up.code;
        page.contentLength = s.length();
        String href;
        Matcher matcher;
        Integer innerLinksCount = 0;
        Integer extLinksCount = 0;
        String metaRobots;
        Boolean haveNoFollow = false;

        Pattern metaRobotsPat = Pattern.compile("<meta name=\"robots\" content=\"([^\"]+)\"\\s*/?>");
        Pattern hrefPat = Pattern.compile("<a href=\"(?:(?!mailto))(.*?)\">(.+?)</a>");
        Pattern descPat = Pattern.compile("<meta name=\"description\" content=\"(.*?)\"/>");
        Pattern keywordsPat = Pattern.compile("<meta name=\"Keywords\" content=\"(.*?)\"/>");
        Pattern titlePat = Pattern.compile("<title>(.*?)</title>");
        Pattern h1Pat = Pattern.compile("<h1[^>]*>(.*?)</h1>");

        // правила мета тэга robots
        if(Options.isUsingMetaRobots()){
            matcher = metaRobotsPat.matcher(s);
            if (matcher.find()) {
                metaRobots = matcher.group(1);
                metaRobots.toLowerCase();
                if(metaRobots.contains("noindex"))
                    return;
                if(metaRobots.contains("nofollow"))
                    haveNoFollow = true;
            }
        }
        //добавляем ссылки только если их удаленность не превышает максимальную заданную и нету nofollow в мета тэге robots
        if (up.distance<Options.getMaxDist() && !haveNoFollow){
            matcher = hrefPat.matcher(s);
            while (matcher.find()) {
                href = matcher.group(1);                
                if(href.length()<"http://".length() || !href.substring(0,7).equals("http://"))
                    href = Downloader.baseURL + href;
                if(href.contains(Downloader.baseDomain)) {
                    innerLinksCount++;
                    Downloader.addToQueue(href,up.distance+1);
                    continue;
                }
                extLinksCount++;
                externalAnchors.add(new Anchor(up.url, href, matcher.group(2)));
            }
        }
        page.innerLinksCount = innerLinksCount;
        page.externalLinksCount = extLinksCount;

        matcher = descPat.matcher(s);
        if (matcher.find())
            page.metaTagDesc = matcher.group(1);

        matcher = descPat.matcher(s);
        if (matcher.find())
            page.metaTagDesc = matcher.group(1);

        matcher = keywordsPat.matcher(s);
        if (matcher.find())
            page.metaTagKeywords = matcher.group(1);

        matcher = titlePat.matcher(s);
        if (matcher.find())
            page.title = matcher.group(1);

        matcher = h1Pat.matcher(s);
        if (matcher.find())
            page.firstH1 = matcher.group(1);

        if(s.contains("_gaq"))
            page.haveGoogleAnalytics = true;

        if(s.contains("<!-- Yandex.Metrika counter -->"))
            page.haveYandexMetrika = true;
        
        page.distance=up.distance;
        page.url=up.url;
        page.state_code=up.stateCode;
        page.state=up.stateString;
        Crawler.pages.add(page);
    }
    
    public Thread getThread() {
        return thread;
    }   
    
    public static boolean isStopped(){
        return stopped;
    }
    
}
